package com.gxc.service.common.components.log.filter;

import com.gxc.service.common.components.log.model.GlobalAccessLog;
import com.gxc.service.common.components.log.model.LogManager;
import com.gxc.service.common.components.log.mq.LogMqSender;
import com.gxc.service.common.model.constant.HeaderConstant;
import com.gxc.service.common.util.IpUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.util.AntPathMatcher;

import javax.annotation.Resource;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;

/**
 * 日志手机过滤器.
 *
 * @author GongXincheng
 * @since 2019-11-08 21:25
 */
@Slf4j
public class LogCollectFilter implements Filter {

    @Value("${spring.application.name}")
    private String applicationName;

    @Resource
    private LogMqSender logMqSender;

    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("{}, init -> applicationName = {}", this.getClass().getSimpleName(), applicationName);
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        // 如果是静态资源，则放行
        if (isStaticUrl(request.getRequestURI())) {
            filterChain.doFilter(request, response);
            return;
        }
        log.warn("{}#doFilter()，request url = {}", this.getClass().getSimpleName(), request.getRequestURL());
        try {
            // 1：将请求信息暂时存放到 ThreadLocal 中，并将requestId放在相应头中
            rememberRequestInfo(request, response);

            filterChain.doFilter(request, servletResponse);
        } catch (Exception e) {
            log.error("request has error：" + e.getMessage(), e);
            rememberExceptionInfo(e);
        } finally {
            // 设置请求消耗时间
            rememberConsumerAndStatus();
            //发送mq存储请求日志
            logMqSender.sendRequestInfoLogMessage(LogManager.getLogInfo());
            // 清空ThreadLocal中的请求日志信息
            LogManager.clearLogInfo();
        }
    }


    /**
     * 判断是否是静态资源的访问
     */
    private boolean isStaticUrl(String requestUri) {
        List<String> staticPath = new ArrayList<>();
        staticPath.add("/bootstrap-3.3.7-dist/**");
        staticPath.add("/css/**");
        staticPath.add("/file/**");
        staticPath.add("/images/**");
        staticPath.add("/js/**");
        staticPath.add("/webjars/**");
        staticPath.add("/fonts/**");
        staticPath.add("/login.html");
        staticPath.add("/favicon.ico");

        for (String path : staticPath) {
            if (antPathMatcher.match(path, requestUri)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 设置请求消耗时间和请求状态码
     */
    private void rememberConsumerAndStatus() {
        GlobalAccessLog logInfo = LogManager.getLogInfo();
        logInfo.setEndTime(new Date());
        logInfo.setConsume(logInfo.getEndTime().getTime() - logInfo.getStartTime().getTime());

        // 如果请求没有发生异常，则表示200
        if (StringUtils.isBlank(logInfo.getException())) {
            logInfo.setHttpStatus(HttpStatus.OK.value());
        }
    }

    /**
     * 将异常信息保存到ThreadLocal中
     */
    private void rememberExceptionInfo(Exception e) {
        GlobalAccessLog logInfo = LogManager.getLogInfo();
        logInfo.setHttpStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
        logInfo.setException(e.getMessage());
    }

    /**
     * 将请求信息暂时存放到 ThreadLocal 中，并将requestId放在相应头中
     */
    private void rememberRequestInfo(HttpServletRequest request, HttpServletResponse response) {
        // 获取ThreadLocal中的请求日志对象
        GlobalAccessLog logInfo = LogManager.getLogInfo();

        // 1：获取请求中的基本信息.
        getRequestDetail(request, logInfo);

        // 2：从请求 header 中获取requestId, 如果为空，生成新的 requestId, 并将requestId放入request域中
        String requestId = getRequestId(request);
        logInfo.setRequestId(requestId);
        // 将 requestId 放到请求相应头中
        response.setHeader(HeaderConstant.REQUEST_ID_HEADER, requestId);

        // 3：获取上一个服务的应用名称
        logInfo.setFromSystem(request.getHeader(HeaderConstant.FROM_SYSTEM_HEADER));
    }

    /**
     * 从请求 header 中获取requestId, 如果为空，生成新的 requestId, 并将requestId放入request域中
     */
    private String getRequestId(HttpServletRequest request) {
        String requestId = request.getHeader(HeaderConstant.REQUEST_ID_HEADER);
        if (StringUtils.isBlank(requestId)) {
            requestId = UUID.randomUUID().toString().replace("-", "");
            // 并将requestId放入request域中
            request.setAttribute(HeaderConstant.REQUEST_ID_HEADER, requestId);
        }
        return requestId;
    }

    /**
     * 获取请求中的信息.
     */
    private void getRequestDetail(HttpServletRequest request, GlobalAccessLog logInfo) {
        String url = request.getRequestURL().toString();
        String uri = request.getRequestURI();
        String hostName = IpUtil.getHostName();
        String httpMethod = request.getMethod();
        String userAgent = request.getHeader(HttpHeaders.USER_AGENT);
        String domainName = request.getHeader(HttpHeaders.HOST);
        String clientIp = IpUtil.getClientIp(request);

        logInfo.setUri(uri);
        logInfo.setUrl(url);
        logInfo.setHostName(hostName);
        logInfo.setHttpMethod(httpMethod);
        logInfo.setUserAgent(userAgent);
        logInfo.setDomainName(domainName);
        logInfo.setClientIp(clientIp);
        logInfo.setStartTime(new Date());
        logInfo.setProjectName(applicationName);
    }

    @Override
    public void destroy() {

    }
}
